package org.jsola.contract.common;

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.ByteArrayBody;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.InputStreamBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.jsola.contract.exception.ResultInfoException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.nio.charset.CodingErrorAction;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * http的请求服务类
 * 也可以考虑使用spring restTemplate调用
 */
public class JzqHttpClientUtils {
    public static Logger logger = LoggerFactory.getLogger(JzqHttpClientUtils.class);
    private static Object objTg = new Object();
    private static JzqHttpClientUtils httpClientUtils;
    /**
     * 默认连接超时时间
     */
    private final static int DEFAULT_CONN_TIMEOUT = 6000;
    /**最大重试次数*/
    private final static int DEFAULT_RETRY_TIMES = 3;

    private CloseableHttpClient client;

    /**
     * ssl trust管理
     */
    public static class SSLTrustAllManager implements X509TrustManager {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }
        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }
        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }
    }

    /**
     * 初始化ssl管理
     * @return
     * @throws KeyStoreException
     * @throws CertificateException
     * @throws NoSuchAlgorithmException
     * @throws IOException
     * @throws KeyManagementException
     */
    private SSLConnectionSocketFactory initSSLConnectionSocketFactory() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, KeyManagementException {
        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        trustStore.load(null, null);
        SSLContext sslcontext = SSLContexts.custom()
                .loadTrustMaterial(trustStore,
                        new TrustSelfSignedStrategy())
                .build();
        sslcontext.init(null, new TrustManager[] { new SSLTrustAllManager() }, null);
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                sslcontext,
                //new String[] { "TLSv1","TLSv1.1","TLSv1.2","SSLv3"},//参考ProtocolVersion//这里写为null为所有
                null,
                null,
                SSLConnectionSocketFactory.getDefaultHostnameVerifier());
        return sslsf;
    }


    public JzqHttpClientUtils() throws Exception {
        // 设置协议http和https对应的处理socket链接工厂的对象
        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.INSTANCE)
                .register("https", initSSLConnectionSocketFactory())
                .build();
        PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        connManager.setValidateAfterInactivity(1000);
        ConnectionConfig connectionConfig = ConnectionConfig.custom()
                .setMalformedInputAction(CodingErrorAction.IGNORE)
                .setUnmappableInputAction(CodingErrorAction.IGNORE)
                .setCharset(Consts.UTF_8)
                .build();
        connManager.setDefaultConnectionConfig(connectionConfig);
        connManager.setMaxTotal(100);
        connManager.setDefaultMaxPerRoute(10);//最大路由深度，即301次数

        //默认头信息,创建自定义的httpclient对象
        List<Header> defaultHeaders=new ArrayList<Header>();
        defaultHeaders.add(new BasicHeader("Accept","text/html,application/xhtml+xml,application/xml,application/json;q=0.9,*/*;q=0.8"));
        defaultHeaders.add(new BasicHeader("Accept-Language","zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2"));
        defaultHeaders.add(new BasicHeader("Connection","close"));
        defaultHeaders.add(new BasicHeader("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0"));
        //默认请求配置
        RequestConfig requestConfig = RequestConfig.custom()
                .setSocketTimeout(DEFAULT_CONN_TIMEOUT)
                .setConnectTimeout(DEFAULT_CONN_TIMEOUT)
                //.setProxy(new HttpHost("myotherproxy", 8080)) //设置代理
                .setConnectionRequestTimeout(DEFAULT_CONN_TIMEOUT).build();
        //重试拦截
        HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() {
            @Override
            public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
                if (executionCount >= DEFAULT_RETRY_TIMES) {
                    //Do not retry if over max retry count
                    return false;
                }
                if (exception instanceof InterruptedIOException) {
                    // Timeout
                    return false;
                }
                if (exception instanceof UnknownHostException) {
                    // Unknown host
                    return false;
                }
                if (exception instanceof ConnectException) {
                    // Connection refused
                    return false;
                }
                if (exception instanceof SSLException) {
                    // SSL handshake exception
                    return false;
                }
                HttpRequest request = (HttpRequest) context.getAttribute(ExecutionContext.HTTP_REQUEST);
                boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
                if (idempotent) {
                    return true;
                }
                return false;
            }
        };

        client = HttpClients.custom()
                .setConnectionManager(connManager)
                .setDefaultHeaders(defaultHeaders)
                .setDefaultRequestConfig(requestConfig)
                .setRetryHandler(myRetryHandler)
                .setRedirectStrategy(new LaxRedirectStrategy())
                .build();
    }
    /**
     * init实例
     * @return
     */
    public static JzqHttpClientUtils init(){
        synchronized (objTg) {
            if (httpClientUtils == null) {
                try {
                    httpClientUtils = new JzqHttpClientUtils();
                } catch (Exception e) {
                    throw new ResultInfoException("ACCESS_SIGN_ERROR","httpClient初始化出错",e);
                }
            }
        }
        return httpClientUtils;
    }


    /**
     * 工具类,通过url和params 生成url地址
     * @param url
     * @param params
     * @return
     * @throws URISyntaxException
     */
    public static URI builderUrl(String url, Map<String, Object> params){
        URIBuilder builder = null;
        try {
            builder = new URIBuilder(url);
            if(params!=null&&params.size()>0){
                for(String key: params.keySet()){
                    Object obj=params.get(key);
                    if(obj==null){continue;}
                    builder.setParameter(key, CommonUtil.parValNoErr(obj, String.class));
                }
            }
            return builder.build();
        } catch (URISyntaxException e) {
           throw new ResultInfoException("HTTP_URL_FORMART","转换地址出错:"+url,e);
        }
    }

    public static void fillHeader(HttpRequestBase request, Map<String, Object> heads){
        //请求头
        if (heads != null && heads.size() > 0) {
            for (String key : heads.keySet()) {
                request.addHeader(key, heads.get(key) + "");
            }
        }
    }
    /**
     * get请求
     * @param uri
     * @param heads
     * @param params
     * @return
     * @throws Exception
     */
    public String getGet(String uri, Map<String, Object> heads, Map<String, Object> params) {
        HttpGet request = new HttpGet(builderUrl(uri,params));
        fillHeader(request,heads);
        CloseableHttpResponse response = null;
        try {
            response = client.execute(request);
            if (response.getStatusLine().getStatusCode() == 200) {
                // 获得返回的字符串
                String result = EntityUtils.toString(response.getEntity(), "UTF-8");
                return result;
            } else {
                String result = EntityUtils.toString(response.getEntity(), "UTF-8");
                result= StringUtils.isNotBlank(result)?result.substring(0,result.length()>200?200:result.length()) : "";
                ResultInfo<String> res=new ResultInfo<String>();
                res.setData(result);
                res.setSuccess(false);
                res.setResultCode(response.getStatusLine().getStatusCode()+"");
                throw new ResultInfoException("HTTP_RESPONSE_ERROR", JSONObject.toJSONString(res));
            }
        } catch (IOException e) {
            throw new ResultInfoException("EXCEPTION","网络请求失败",e);
        } finally {
            try {
                response.close();
            } catch (Exception e) {}
        }
    }

    public byte[] getGetByte(String uri, Map<String, Object> heads, Map<String, Object> params) {
        HttpGet request = new HttpGet(builderUrl(uri,params));
        fillHeader(request,heads);
        CloseableHttpResponse response = null;
        try {
            response = client.execute(request);
            if (response.getStatusLine().getStatusCode() == 200) {
                // 获得返回的字符串
                byte[] result = EntityUtils.toByteArray(response.getEntity());
                return result;
            } else {
                ResultInfo<String> res=new ResultInfo<String>();
                res.setSuccess(false);
                res.setResultCode(response.getStatusLine().getStatusCode()+"");
                throw new ResultInfoException("HTTP_RESPONSE_ERROR", JSONObject.toJSONString(res));
            }
        } catch (IOException e) {
            throw new ResultInfoException("EXCEPTION","网络请求失败",e);
        } finally {
            try {
                response.close();
            } catch (Exception e) {}
        }
    }

    /**
     * 直接获取返回体,注意要手动关闭response
     * @param uri
     * @param heads
     * @param params
     * @return
     */
    public CloseableHttpResponse getGetResponse(String uri, Map<String, Object> heads, Map<String, Object> params) throws IOException {
        HttpGet request = new HttpGet(builderUrl(uri,params));
        fillHeader(request,heads);
        CloseableHttpResponse response = null;
        response = client.execute(request);
        return response;
    }

    /**
     * 构建post的body信息,
     * @param request
     * @param params
     * @param ifMutipart 是否启用富文件方式上传
     */
    private void buildPostBody(HttpPost request, Map<String, Object> params, boolean ifMutipart){
        if(ifMutipart){
            //富文本请求
            MultipartEntityBuilder meBuiler = MultipartEntityBuilder.create();
            if (params != null && params.size() > 0) {
                for (String key : params.keySet()) {
                    Object obj = params.get(key);
                    if(obj==null){
                        continue;
                    }else if (obj instanceof File) {
                        FileBody fb = new FileBody((File) obj);
                        meBuiler.addPart(key, fb);
                    } else if (obj instanceof ByteArrayBody) {
                        meBuiler.addPart(key, (ByteArrayBody) obj);
                    } else if (obj instanceof FileBody) {
                        meBuiler.addPart(key, (FileBody) obj);
                    } else if (obj instanceof InputStreamBody) {
                        meBuiler.addPart(key, (InputStreamBody) obj);
                    }else {
                        StringBody sb = new StringBody(CommonUtil.parValNoErrDef(obj, String.class, ""), ContentType.create("text/plain", "UTF-8"));
                        meBuiler.addPart(key, sb);
                    }
                }
            }
            HttpEntity httpEntity = meBuiler.build();
            request.setEntity(httpEntity);
        }else{
            //普通请求
            List<NameValuePair> pList = new ArrayList<NameValuePair>();
            if (params != null && params.size() > 0) {
                for (String key : params.keySet()) {
                    Object obj=params.get(key);
                    if(obj==null){continue;}
                    pList.add(new BasicNameValuePair(key, obj + ""));
                }
            }
            try {
                request.setEntity(new UrlEncodedFormEntity(pList, "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                throw new ResultInfoException("HTTP_REQ_ENCODE_ERROR","请求参数格式化出错",e);
            }
        }
    }

    /**
     * post的上传请求
     * @param uri
     * @param heads
     * @param params
     * @return
     * @throws Exception
     */
    public String getPost(String uri, Map<String, Object> heads, Map<String, Object> params, boolean ifMutipart) {
        HttpPost request = new HttpPost(uri);
        fillHeader(request,heads);
        buildPostBody(request,params,ifMutipart);
        CloseableHttpResponse response = null;
        try {
            response = client.execute(request);
            if (response.getStatusLine().getStatusCode() == 200) {
                // 获得返回的字符串
                String result = EntityUtils.toString(response.getEntity(), "UTF-8");
                return result;
            } else {
                String result = EntityUtils.toString(response.getEntity(), "UTF-8");
                result= StringUtils.isNotBlank(result) ?result.substring(0,result.length()>200?200:result.length()) : "";
                ResultInfo<String> res=new ResultInfo<String>();
                res.setData(result);
                res.setSuccess(false);
                res.setResultCode(response.getStatusLine().getStatusCode()+"");
                throw new ResultInfoException("HTTP_RESPONSE_ERROR", JSONObject.toJSONString(res));
            }
        } catch (IOException e) {
            throw new ResultInfoException("HTTP_IO_ERROR","网络请求失败",e);
        } finally {
            try {
                response.close();
            } catch (Exception e) {}
        }
    }

    /**
     * post的上传请求，返回byte[]
     * @param uri
     * @param heads
     * @param params
     * @return
     * @throws Exception
     */
    public byte[] getPostByte(String uri, Map<String, Object> heads, Map<String, Object> params, boolean ifMutipart) {
        HttpPost request = new HttpPost(uri);
        fillHeader(request,heads);
        buildPostBody(request,params,ifMutipart);
        CloseableHttpResponse response = null;
        try {
            response = client.execute(request);
            if (response.getStatusLine().getStatusCode() == 200) {
                // 获得返回的byte[]
                byte[] result = EntityUtils.toByteArray(response.getEntity());
                return result;
            } else {
                ResultInfo<String> res=new ResultInfo<String>();
                res.setSuccess(false);
                res.setResultCode(response.getStatusLine().getStatusCode()+"");
                throw new ResultInfoException("HTTP_RESPONSE_ERROR", JSONObject.toJSONString(res));
            }
        } catch (IOException e) {
            throw new ResultInfoException("HTTP_IO_ERROR","网络请求失败",e);
        } finally {
            try {
                response.close();
            } catch (Exception e) {}
        }
    }

    /**
     * post的上传请求，返回CloseableHttpResponse,必须手动关闭
     * @param uri
     * @param heads
     * @param params
     * @return
     * @throws Exception
     */
    public CloseableHttpResponse getPostResponse(String uri, Map<String, Object> heads, Map<String, Object> params, boolean ifMutipart) {
        HttpPost request = new HttpPost(uri);
        fillHeader(request,heads);
        buildPostBody(request,params,ifMutipart);
        CloseableHttpResponse response = null;
        try {
            response = client.execute(request);
            if (response.getStatusLine().getStatusCode() == 200) {
                return response;
            } else {
                String result = EntityUtils.toString(response.getEntity(), "UTF-8");
                result= StringUtils.isNotBlank(result)?result.substring(0,result.length()>200?200:result.length()) : "";
                ResultInfo<String> res=new ResultInfo<String>();
                res.setData(result);
                res.setSuccess(false);
                res.setResultCode(response.getStatusLine().getStatusCode()+"");
                throw new ResultInfoException("HTTP_RESPONSE_ERROR", JSONObject.toJSONString(res));
            }
        } catch (IOException e) {
            throw new ResultInfoException("HTTP_IO_ERROR","网络请求失败",e);
        } finally {
            try {
                response.close();
            } catch (Exception e) {}
        }
    }
}
